
import edu.wpi.first.smartdashboard.gui.StaticWidget;
import edu.wpi.first.smartdashboard.gui.Widget;
import edu.wpi.first.smartdashboard.properties.Property;
import edu.wpi.first.smartdashboard.robot.Robot;
import edu.wpi.first.smartdashboard.types.DataType;
import edu.wpi.first.wpilibj.networking.NetworkAdditionListener;
import edu.wpi.first.wpilibj.networking.NetworkListener;
import edu.wpi.first.wpilibj.networking.NetworkTable;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.*;
import javax.swing.table.AbstractTableModel;

import javax.swing.table.TableRowSorter;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

/*
 * To change this template, choose Tools | Templates and open the template in
 * the editor.
 */
/**
 *
 * @author tlemmons
 */
public class TargetData extends Widget implements NetworkListener, NetworkAdditionListener {

    public static final DataType[] TYPES = {DataType.NUMBER};
    public static final String DELETED_VALUE = "\"";
    public static final String NAME = "Target Data";
    private JTable table;
    private TargetData.TargetTableModel model;
    private Map<String, Map<String, Double>> values;
    private JButton save;
    private JButton clear;
    private JButton remove;
    private JButton Over;
    private JButton FarOver;
    private JButton Under;
    private JButton FarUnder;
    private JButton RightON;
    NetworkTable targetTable;
    TableRowSorter mySort;

    public TargetData() {
        targetTable = NetworkTable.getTable("targetTable");
    }

    @Override
    public void init() {
        Comparator<String> myCompare = new Comparator<String>() {

            public int compare(String s1, String s2) {
                Double d1 = Double.parseDouble(s1);
                Double d2 = Double.parseDouble(s2);
                return Double.compare(d1, d2);

            }
        };

        clear = new JButton("Clear");
        clear.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                if (table.isEditing()) {
                    table.getCellEditor().cancelCellEditing();
                }
                Map.Entry<String, Map<String, Double>> entry = model.getRow(0);
                do {
                    if (entry != null) {
                        model.delete(entry.getKey());
                    }
                    model.getRow(0);
                } while (entry != null);
            }
        });

        remove = new JButton("Remove");
        remove.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                if (table.isEditing()) {
                    table.getCellEditor().cancelCellEditing();
                }
                Map.Entry<String, Map<String, Double>> entry = model.getRow(table.getSelectedRow());
                if (entry != null) {
                    model.delete(entry.getKey());
                }
            }
        });

        save = new JButton("Save");
        save.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                targetTable.putBoolean(Robot.PREF_SAVE_FIELD, true);
                model.save();
            }
        });

        Over = new JButton("Over");
        Over.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                mySort.sort();

            }
        });
        FarOver = new JButton("Far Over");
        FarOver.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
            }
        });
        Under = new JButton("Under");
        Under.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
            }
        });
        FarUnder = new JButton("Far Under");
        FarUnder.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
            }
        });
        RightON = new JButton("Went In");
        RightON.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
            }
        });

        values = new LinkedHashMap<>();

        targetTable.addAdditionListener(this, true);
        targetTable.addListenerToAll(this);

        model = new TargetData.TargetTableModel();

        table = new JTable(model);
        table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        table.getTableHeader().setReorderingAllowed(false);

        table.setAutoCreateRowSorter(true);
        mySort = (TableRowSorter) table.getRowSorter();//new TableRowSorter(table.getModel());
        mySort.setSortsOnUpdates(true);
        mySort.setComparator(0, myCompare);
        List<RowSorter.SortKey> sortKeys = new ArrayList<RowSorter.SortKey>();
        sortKeys.add(new RowSorter.SortKey(0, SortOrder.ASCENDING));
        mySort.setSortKeys(sortKeys);

        JPanel buttonPanel = new JPanel();
        buttonPanel.setLayout(new GridLayout(0, 4));
        buttonPanel.add(clear);
        buttonPanel.add(remove);
        buttonPanel.add(Over);
        buttonPanel.add(FarOver);
        buttonPanel.add(Under);
        buttonPanel.add(FarUnder);
        buttonPanel.add(RightON);

        JPanel controlPanel = new JPanel();
        controlPanel.setLayout(new BorderLayout());
        controlPanel.add(buttonPanel, BorderLayout.NORTH);
        controlPanel.add(save, BorderLayout.SOUTH);

        setLayout(new BorderLayout());
        JScrollPane tableScrollPane = new JScrollPane(table, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
        add(tableScrollPane, BorderLayout.CENTER);
        add(controlPanel, BorderLayout.SOUTH);

        setPreferredSize(new Dimension(400, 200));
        mySort.sort();
        tableScrollPane.repaint();
        table.setEnabled(false);
        tableScrollPane.setEnabled(false);
        table.setColumnSelectionAllowed(false);
        table.setRowSelectionAllowed(true);
    }

    @Override
    public void disconnect() {
        targetTable.removeAdditionListener(this);
        targetTable.removeListenerFromAll(this);
    }

    @Override
    public void propertyChanged(Property property) {
        mySort.sort();
    }

    @Override
    public void valueChanged(String key, Object value) {
        


            if (DELETED_VALUE.equals(value.toString())) {
                values.remove(key);
            } else {


                NetworkTable myTab = (NetworkTable) value;
                Map<String, Double> myMap = new LinkedHashMap<>();
                try {
                    myMap.put("OverFar", myTab.getDouble("OverFar"));
                } catch (NoSuchElementException e) {
                }
                try {
                    myMap.put("OverNear", myTab.getDouble("OverNear"));
                } catch (NoSuchElementException e) {
                }
                try {
                    myMap.put("Score", myTab.getDouble("Score"));
                } catch (NoSuchElementException e) {
                }
                try {
                    myMap.put("UnderFar", myTab.getDouble("UnderFar"));
                } catch (NoSuchElementException e) {
                }
                try {
                    myMap.put("UnderNear", myTab.getDouble("UnderNear"));
                    values.put(key, myMap);
                } catch (NoSuchElementException e) {
                }
            }
        if (model != null) {
            model.fireTableDataChanged();
            model.save();
        }
    }

    @Override
    public void valueConfirmed(String key, Object value) {
        valueChanged(key, value);
    }

    @Override
    public void fieldAdded(String key, Object value) {
        valueChanged(key, value);
    }

    @Override
    public void setValue(Object o) {
        int i = 0;
        double dist = (double) o;
        for (i = 0; i < table.getRowCount() - 1; i++) {
            Object d1 = table.getValueAt(i, 0);
            double d = Double.parseDouble(d1.toString());
            if (dist <= d) {
                break;
            }
        }
        if (i < table.getRowCount()) {
            table.setRowSelectionInterval(i, i);
            table.scrollRectToVisible(table.getCellRect((i + 2) < table.getRowCount() ? (i + 2) : table.getRowCount(), 0, true));
            table.scrollRectToVisible(table.getCellRect(((i - 2) > 0) ? (i - 2) : i, 0, true));
        }

//        table.set
    }

    private class TargetTableModel extends AbstractTableModel {
   private String getTagValue(String sTag, Element eElement) {
	NodeList nlList = eElement.getElementsByTagName(sTag).item(0).getChildNodes();
 
        Node nValue = (Node) nlList.item(0);
 
	return nValue.getNodeValue();
  }

        public TargetTableModel() {
            //load xml data
            	DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
		DocumentBuilder dBuilder;
            try {
                dBuilder = dbFactory.newDocumentBuilder();
 		Document doc = dBuilder.parse("target.xml");
		//doc.getDocumentElement().normalize();
 		NodeList nList = doc.getElementsByTagName("Record");
		System.out.println("-----------------------");
                int count=nList.getLength();
		for (int temp = 0; temp < count; temp++) {
 
		   Node nNode = nList.item(temp);

		      Element eElement = (Element) nNode;
                        String key=eElement.getAttribute("Dist");
                        Map<String, Double> map=new LinkedHashMap<String, Double>();
                        map.put("OverFar", Double.parseDouble(getTagValue("OverFar", eElement)));
                        map.put("OverNear", Double.parseDouble(getTagValue("OverNear", eElement)));
                        map.put("Score",Double.parseDouble(getTagValue("Score", eElement)));
                        map.put("UnderFar", Double.parseDouble(getTagValue("UnderFar", eElement)));
                        map.put("UnderNear", Double.parseDouble(getTagValue("UnderNear", eElement)));
                        count=nList.getLength();
                        this.put(key, map);
                        values.put(key, map);
                }
           } catch (SAXException ex) {
                Logger.getLogger(TargetData.class.getName()).log(Level.SEVERE, null, ex);
            } catch (IOException ex) {
                Logger.getLogger(TargetData.class.getName()).log(Level.SEVERE, null, ex);
            } catch (ParserConfigurationException ex) {
                Logger.getLogger(TargetData.class.getName()).log(Level.SEVERE, null, ex);
            }

        }

        @Override
        public int getRowCount() {
            return values.size();
        }

        @Override
        public int getColumnCount() {
            return 6;
        }

        @Override
        public String getColumnName(int i) {
            switch (i) {
                case 0:
                    return "Distance";
                case 1:
                    return "MadeIt";
                case 2:
                    return "NUnder";
                case 3:
                    return "FUnder";
                case 4:
                    return "NOver";
                case 5:
                    return "FOver";
                default:
                    return "ERROR";
            }
        }

        public Map.Entry<String, Map<String, Double>> getRow(int rowIndex) {
            int row = 0;
            for (Map.Entry<String, Map<String, Double>> entry : values.entrySet()) {
                if (row++ == rowIndex) {
                    return entry;
                }
            }
            return null;
        }

        public boolean put(String key, Map<String, Double> value) {
            if (validateKey(key)) {
                NetworkTable tableRef = new NetworkTable();
                tableRef.putDouble("OverFar", value.get("OverFar"));
                tableRef.putDouble("OverNear", value.get("OverNear"));
                tableRef.putDouble("Score", value.get("Score"));
                tableRef.putDouble("UnderFar", value.get("UnderFar"));
                tableRef.putDouble("UnderNear", value.get("UnderNear"));
                targetTable.putSubTable(key, tableRef);
                //save();

                return true;
            }
            return false;
        }

        public void save() {
            try {
                DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance();
                DocumentBuilder builder = domFactory.newDocumentBuilder();

                Document doc = builder.newDocument();
                Element e = doc.createElement("map");
                doc.appendChild(e);
                try {
                    for (Map.Entry<String, Map<String, Double>> entry : values.entrySet()) {
                        Element i = doc.createElement("Record");
                        i.setAttribute("Dist", entry.getKey());
                        for (Map.Entry<String, Double> trys : entry.getValue().entrySet()) {
                            Element j = doc.createElement(trys.getKey());
                            j.setTextContent(trys.getValue().toString());
                            i.appendChild(j);
                        }
                        e.appendChild(i);
                        //doc.appendChild(XmlElementForObject(doc, values, null));
                    }
                } catch (Exception ex) {
                    Logger.getLogger(TargetData.class.getName()).log(Level.SEVERE, null, ex);
                }
                Transformer transformer = TransformerFactory.newInstance().newTransformer();
                transformer.setOutputProperty(OutputKeys.INDENT, "yes");
                StreamResult result = new StreamResult(new File("target.xml"));
                DOMSource source = new DOMSource(doc);
                transformer.transform(source, result);

            } catch (TransformerException ex) {
                Logger.getLogger(TargetData.class.getName()).log(Level.SEVERE, null, ex);
            } catch (ParserConfigurationException ex) {
                Logger.getLogger(TargetData.class.getName()).log(Level.SEVERE, null, ex);
            }

        }

        public void delete(String key) {
            targetTable.putString(key, DELETED_VALUE);
        }

        public boolean validateKey(String key) {
            if (key.isEmpty()) {
                JOptionPane.showMessageDialog(TargetData.this, "The key cannot be empty", "Bad Key", JOptionPane.ERROR_MESSAGE);
                return false;
            }
            if (key.contains(" ") || key.contains("=") || key.contains("\t") || key.contains("\r") || key.contains("\n")) {
                JOptionPane.showMessageDialog(TargetData.this, "The key cannot containt ' ', '=', tabs or newlines", "Bad Key", JOptionPane.ERROR_MESSAGE);
                return false;
            }
            return true;
        }

        public boolean validateValue(String value) {
            if (value.contains("\"")) {
                JOptionPane.showMessageDialog(TargetData.this, "The value cannot contain '\"'", "Bad Value", JOptionPane.ERROR_MESSAGE);
                return false;
            }
            return true;
        }

        @Override
        public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
            if (columnIndex == 0) {//Key
                Map.Entry<String, Map<String, Double>> entry = getRow(rowIndex);
                if (entry != null) {
                    String oldName = entry.getKey();
                    Map<String, Double> value = entry.getValue();
                    if (!oldName.equals(aValue.toString())) {
                        if (!values.containsKey(aValue.toString())) {

                            if (put(aValue.toString(), value)) {
                                delete(oldName);
                            }
                        } else {
                            JOptionPane.showMessageDialog(TargetData.this, "An entry with the key " + aValue + " already exists", "Duplicate Key", JOptionPane.ERROR_MESSAGE);
                        }
                    }
                }
            } else {//Value
                Map.Entry<String, Map<String, Double>> entry = getRow(rowIndex);
                if (entry != null) {
                    put(entry.getKey(), (Map<String, Double>) aValue);
                }
            }
        }

        @Override
        public boolean isCellEditable(int rowIndex, int columnIndex) {
            return rowIndex >= 0;
        }

        @Override
        public Object getValueAt(int rowIndex, int columnIndex) {
            Map.Entry<String, Map<String, Double>> entry = getRow(rowIndex);
            if (entry != null) {
                if (columnIndex == 0) {
                    return entry.getKey();
                }
                Map<String, Double> columns = entry.getValue();
                switch (columnIndex) {
                    case 1:
                        return columns.get("Score");

                    case 2:
                        return columns.get("UnderNear");

                    case 3:
                        return columns.get("UnderFar");

                    case 4:
                        return columns.get("OverNear");

                    case 5:
                        return columns.get("OverFar");
                }
            }
            return "ERROR";
        }

        Element XmlElementForObject(Document doc, Object obj, String nullStr) throws Exception {
            if (obj == null) {
                return doc.createElement(nullStr);
            }
            if (obj instanceof String) {
                Element e = doc.createElement("p");
                //Element e = doc.createElement(obj.getClass().getSimpleName());
                e.setAttribute("t", obj.getClass().getSimpleName());
                e.setAttribute("v", obj.toString());
                return e;
            }

            if (obj.getClass().isArray()) {
                Element e = doc.createElement("arr");

                Object[] arr = (Object[]) obj;
                for (Object o : arr) {
                    e.appendChild(XmlElementForObject(doc, o, nullStr));
                }
                return e;
            }

            if (obj instanceof Collection) {
                Element e = doc.createElement("arr");

                Object[] arr = ((Collection) obj).toArray();
                for (Object o : arr) {
                    e.appendChild(XmlElementForObject(doc, o, nullStr));
                }
                return e;
            }
//        if(obj instanceof java.util.LinkedHashMap.Entry){
//            
//        }

            if (obj instanceof Map) {
                Element e = doc.createElement("map");

                Object[] keys = ((Map) obj).keySet().toArray();
                for (Object key : keys) {
                    Element i = doc.createElement("i");
                    i.appendChild(XmlElementForObject(doc, key, nullStr));
                    i.appendChild(XmlElementForObject(doc, ((Map) obj).get(key), nullStr));
                    e.appendChild(i);
                }

                return e;
            }

            Element e = doc.createElement(obj.getClass().getSimpleName());

            for (java.lang.reflect.Field fld : obj.getClass().getFields()) {
                if (fld.get(obj) == null) {
                    Element f = doc.createElement(fld.getName());
                    f.setAttribute("v", nullStr);
                    e.appendChild(f);
                    continue;
                }
                if (fld.getType().isPrimitive() || fld.get(obj) instanceof String) {
                    Element f = doc.createElement(fld.getName());
                    f.setAttribute("t", fld.getType().getSimpleName());
                    f.setAttribute("v", fld.get(obj).toString());
                    e.appendChild(f);
                    continue;
                }

                Element f = doc.createElement(fld.getName());
                f.appendChild(XmlElementForObject(doc, fld.get(obj), nullStr));
                e.appendChild(f);

            }
            return e;

        }
    }
}
